{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Define the Winner Take All units\n",
    "class WTU(object):\n",
    "    \n",
    "    #_learned = False\n",
    "    \n",
    "    def __init__(self, m, n, dim, num_iterations, eta = 0.5, sigma = None):\n",
    "        \"\"\"\n",
    "        m x n : The dimension of 2D lattice in which neurons are arranged\n",
    "        dim : Dimension of input training data\n",
    "        num_iterations: Total number of training iterations\n",
    "        eta : Learning rate\n",
    "        sigma: The radius of neighbourhood function.\n",
    "        \"\"\"\n",
    "        self._m = m\n",
    "        self._n = n\n",
    "        self._neighbourhood = []\n",
    "        self._topography = []\n",
    "        self._num_iterations = int(num_iterations) \n",
    "        self._learned = False\n",
    "        \n",
    "        eta = float(eta)\n",
    "           \n",
    "        if sigma is None:\n",
    "            sigma = max(m,n)/2.0    # Constant radius\n",
    "        else:\n",
    "            sigma = float(sigma)\n",
    "        \n",
    "            \n",
    "        print('Network created with dimensions',m,n)\n",
    "            \n",
    "        self._graph = tf.Graph()\n",
    "        \n",
    "        # Build Computation Graph of SOM\n",
    "        \n",
    "        with self._graph.as_default():\n",
    "            # Weight Matrix and the topography of neurons\n",
    "            self._W = tf.Variable(tf.random_normal([m*n, dim], seed = 0))\n",
    "            self._topography = tf.constant(np.array(list(self._neuron_location(m, n))))\n",
    "            \n",
    "            # Placeholders for training data\n",
    "            self._X = tf.placeholder('float', [dim])\n",
    "            \n",
    "            # Placeholder to keep track of number of iterations\n",
    "            self._iter = tf.placeholder('float')\n",
    "            \n",
    "            # Finding the Winner and its location\n",
    "            d = tf.sqrt(tf.reduce_sum(tf.pow(self._W - tf.stack([self._X \n",
    "                                                for i in range(m*n)]),2),1))\n",
    "            self.WTU_idx = tf.argmin(d,0)\n",
    "            \n",
    "            slice_start = tf.pad(tf.reshape(self.WTU_idx, [1]),np.array([[0,1]]))\n",
    "            self.WTU_loc = tf.reshape(tf.slice(self._topography, slice_start,[1,2]), [2])\n",
    "            \n",
    "            \n",
    "            # Change learning rate and radius as a function of iterations\n",
    "            learning_rate = 1 - self._iter/self._num_iterations\n",
    "            _eta_new = eta * learning_rate\n",
    "            _sigma_new = sigma * learning_rate\n",
    "            \n",
    "            \n",
    "            # Calculating Neighbourhood function\n",
    "            distance_square = tf.reduce_sum(tf.pow(tf.subtract(\n",
    "                self._topography, tf.stack([self.WTU_loc for i in range(m * n)])), 2), 1)\n",
    "            neighbourhood_func = tf.exp(tf.negative(tf.div(tf.cast(\n",
    "                distance_square, \"float32\"), tf.pow(_sigma_new, 2))))\n",
    "            \n",
    "            # multiply learning rate with neighbourhood func\n",
    "            eta_into_Gamma = tf.multiply(_eta_new, neighbourhood_func)\n",
    "            \n",
    "            # Shape it so that it can be multiplied to calculate dW\n",
    "            weight_multiplier = tf.stack([tf.tile(tf.slice(\n",
    "                eta_into_Gamma, np.array([i]), np.array([1])), [dim])\n",
    "                for i in range(m * n)])\n",
    "            delta_W = tf.multiply(weight_multiplier,\n",
    "                tf.subtract(tf.stack([self._X for i in range(m * n)]),self._W))\n",
    "            new_W = self._W + delta_W\n",
    "            self._training = tf.assign(self._W,new_W)\n",
    "            \n",
    "            # Initialize All variables\n",
    "            init = tf.global_variables_initializer()\n",
    "            \n",
    "            self._sess = tf.Session()\n",
    "            self._sess.run(init)\n",
    "            \n",
    "    def fit(self, X):\n",
    "        \"\"\"\n",
    "        Function to carry out training\n",
    "        \"\"\"\n",
    "        for i in range(self._num_iterations):\n",
    "            for x in X:\n",
    "                self._sess.run(self._training, feed_dict= {self._X:x, self._iter: i})\n",
    "            \n",
    "        \n",
    "        \n",
    "        # Store a centroid grid for easy retreival\n",
    "        centroid_grid = [[] for i in range(self._m)]\n",
    "        self._Wts = list(self._sess.run(self._W))\n",
    "        self._locations = list(self._sess.run(self._topography))\n",
    "        for i, loc in enumerate(self._locations):\n",
    "            centroid_grid[loc[0]].append(self._Wts[i])\n",
    "        self._centroid_grid = centroid_grid\n",
    "\n",
    "        self._learned = True\n",
    "    \n",
    "    def winner(self, x):\n",
    "        idx = self._sess.run([self.WTU_idx,self.WTU_loc], feed_dict = {self._X:x})\n",
    "        return idx\n",
    "             \n",
    "    def _neuron_location(self,m,n):\n",
    "        \"\"\"\n",
    "        Function to generate the 2D lattice of neurons\n",
    "        \"\"\"\n",
    "        for i in range(m):\n",
    "            for j in range(n):\n",
    "                yield np.array([i,j])\n",
    "                \n",
    "                \n",
    "    def get_centroids(self):\n",
    "        \"\"\"\n",
    "        Function to return a list of 'm' lists, with each inner list containing\n",
    "        the 'n' corresponding centroid locations as 1-D NumPy arrays.\n",
    "        \"\"\"\n",
    "        if not self._learned:\n",
    "            raise ValueError(\"SOM not trained yet\")\n",
    "        return self._centroid_grid\n",
    "\n",
    "    def map_vects(self, X):\n",
    "        \"\"\"\n",
    "        Function to map each input vector to the relevant neuron in the lattice\n",
    "        \"\"\"\n",
    "\n",
    "        if not self._learned:\n",
    "            raise ValueError(\"SOM not trained yet\")\n",
    "\n",
    "        to_return = []\n",
    "        for vect in X:\n",
    "            min_index = min([i for i in range(len(self._Wts))],\n",
    "                            key=lambda x: np.linalg.norm(vect -\n",
    "                                                         self._Wts[x]))\n",
    "            to_return.append(self._locations[min_index])\n",
    "\n",
    "        return to_return\n",
    "\n",
    "\n",
    "            \n",
    "        \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def normalize(df):\n",
    "    result = df.copy()\n",
    "    for feature_name in df.columns:\n",
    "        max_value = df[feature_name].max()\n",
    "        min_value = df[feature_name].min()\n",
    "        result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)\n",
    "    return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "## Reading input data from file\n",
    "import pandas as pd\n",
    "\n",
    "df = pd.read_csv('colors.csv')  # The last column of data file is a label\n",
    "data = normalize(df[['R', 'G', 'B']]).values\n",
    "name = df['Color-Name'].values\n",
    "n_dim = len(df.columns) - 1\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Data for Training\n",
    "colors = data\n",
    "color_names = name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network created with dimensions 30 30\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nYII=\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x1c01f139e80>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "som = WTU(30, 30, n_dim, 400, sigma=10.0)\n",
    "som.fit(colors)\n",
    "\n",
    "# Get output grid\n",
    "image_grid = som.get_centroids()\n",
    "\n",
    "# Map colours to their closest neurons\n",
    "mapped = som.map_vects(colors)\n",
    "\n",
    "# Plot\n",
    "plt.imshow(image_grid)\n",
    "plt.title('Color Grid SOM')\n",
    "for i, m in enumerate(mapped):\n",
    "    plt.text(m[1], m[0], color_names[i], ha='center', va='center',\n",
    "             bbox=dict(facecolor='white', alpha=0.5, lw=0))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "495 [16 15]\n"
     ]
    }
   ],
   "source": [
    "idx, loc = som.winner([0.5, 0.5, 0.5])\n",
    "print(idx, loc)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:tf-gpu-1p3]",
   "language": "python",
   "name": "conda-env-tf-gpu-1p3-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
